const Record = require("./Record.js");
const Bus = require("ws-event-bus").default;
const chalk = require("chalk");
const orange = chalk.keyword("orange");
let draggindEle = null;
// function addBtnEle(id, fn) {
//   if (document.getElementById(id)) {
//     return document.getElementById(id);
//   }
//   const box = getBtnBox();
//   const btn = document.createElement("p");
//   btn.id = id;
//   btn.style.margin = "0 0 2px";
//   btn.style.zIndex = 9999;
//   btn.style.fontSize = "16px";
//   btn.style.lineHeight = "1";
//   btn.style.padding = "5px 10px";
//   btn.style.border = "none";
//   btn.style.borderRadius = "5px";
//   btn.style.background = "#E6A23C";
//   btn.style.color = "#fff";
//   btn.style.cursor = "pointer";
//   btn.style.outline = "none";
//   btn.style.transition = "all 0.2s";
//   btn.style.opacity = "1";
//   btn.style.textAlign = "center";
//   btn.innerHTML = id;
//   btn.onclick = function () {
//     if (typeof fn == "function") {
//       if (btn.isDragging) {
//         btn.isDragging = false;
//         return;
//       }
//       fn();
//     }
//   };
//   box.appendChild(btn);
//   box.lastChild.style.margin = "0";
//   return btn;
// }

// function getBtnBox() {
//   if (document.getElementById("logic-topo-btns-box")) {
//     return document.getElementById("logic-topo-btns-box");
//   }
//   const box = document.createElement("div");
//   box.id = "logic-topo-btns-box";
//   box.style.position = "fixed";
//   box.style.top = "20px";
//   box.style.right = "20px";
//   box.style.zIndex = 9999;
//   box.style.border = "none";
//   box.style.outline = "none";
//   box.style.display = "flex";
//   box.style.flexDirection = "column";
//   box.style.maxHeight = "100px";
//   box.style.overflow = "auto";
//   box.style.boxShadow = "0 0 5px rgba(0,0,0,0.5)";
//   box.style.borderRadius = "5px";
//   box.style.padding = "0";
//   box.style.width = "fit-content";
//   draggable(box);
//   window.document.body.appendChild(box);
//   return box;
// }

// function draggable(ele) {
//   ele.onmousedown = function (e) {
//     draggindEle = ele;
//     ele.style.position = "fixed";
//     ele.style.zIndex = 9999;
//   };
//   ele.onmouseup = function (e) {
//     draggindEle = null;
//   };

//   window.onmousemove = function (e) {
//     if (draggindEle) {
//       draggindEle.style.right = "auto";
//       draggindEle.style.bottom = "auto";
//       draggindEle.style.left = e.clientX - draggindEle.clientWidth / 2 + "px";
//       draggindEle.style.top = e.clientY - draggindEle.clientHeight / 2 + "px";
//       draggindEle.isDragging = true;
//     }
//   };
//   window.onmouseup = function (e) {
//     draggindEle = null;
//   };
// }

let id = 0;

function getHost(url) {
  let [host] = url.split("?");
  host = (host.trim().match(/((http|ws)(s)?:\/\/([^/]+))(\/)?.*/) || [])[4]; // 获取调试服务器host
  return host;
}

function stringifyCirircular(obj) {
  let cache = [];
  let str = JSON.stringify(obj, (key, value) => {
    if (typeof value === "object" && value !== null) {
      if (cache.indexOf(value) !== -1) {
        return;
      }
      cache.push(value);
    }
    return value;
  });
  cache = null;
  return str;
}

function checkJSON(data) {
  try {
    if (data && typeof data == "object") {
      return data;
    } else {
      console.log("error data type: ", data);
    }
  } catch (e) {
    console.log("error data : ", data);
  }
  return data;
}
const cacheWS = {};
const watchUrl = {};
const initWS = (url, callback) => {
  try {
    watchUrl[url] = watchUrl[url] || new Set();
    watchUrl[url].add(callback);
    if (!cacheWS[url]) {
      cacheWS[url] = new Bus({
        id: "connector",
        url: url || "ws://localhost:9503",
        log: false
      });
      console.$log(url, "cacheWS[url]");
      let ws = cacheWS[url];
      ws.$status = {
        status: "wait",
      };
      ws.on("open", () => {
        if (ws.$status.status != "open") {
          ws.$status.status = "open";
          watchUrl[url].forEach((cb) => cb(ws.$status.status));
        }
        callback(ws.$status.status);
      });
      ws.on("error", () => {
        if (ws.$status.status != "error") {
          ws.$status.status = "error";
          watchUrl[url].forEach((cb) => cb(ws.$status.status));
          delete cacheWS[url];
        }
        callback(ws.$status.status);
      });
      ws.on("close", () => {
        if (ws.$status.status != "close") {
          ws.$status.status = "close";
          watchUrl[url].forEach((cb) => cb(ws.$status.status));
          delete cacheWS[url];
        }
        callback(ws.$status.status);
      });
      ws.on("message", (e, req) => {
        //这里监听调试器页面的开启关闭
        if (
          req &&
          req.ids &&
          req.ids.includes("ws-event-handler") &&
          ws.$status.status != "ready"
        ) {
          ws.$status.status = "ready";
          watchUrl[url].forEach((cb) => cb(ws.$status.status));
        }
        callback(ws.$status.status);
      });
    } else {
      callback(cacheWS[url].$status.status);
    }
    return cacheWS[url];
  } catch (e) {
    callback("error");
  }
};
let opened = {};

class RecordWS {
  init(host) {
    return new Promise((resolve, reject) => {
      try {
        const url = "ws://" + host.replace(/(http|ws)(s)?:\/\//, "");
        initWS(url, (status) => {
          if (status == "ready" || status == "open") {
            resolve();
          }
        });
      } catch (e) {
        console.$log("链接调试器错误：", e);
        reject("error");
      }
    });
  }
  getWs(url) {
    return cacheWS['ws://' + getHost(url)];
  }
  async eachProcess() {
    try {
      if (!this.record.$getBufferLength()) return;
      if (Date.now() - this.lastTime < 1) {
        clearTimeout(this.throttleEach);
        this.throttleEach = setTimeout(() => {
          this.eachProcess();
        })
        return;
      }
      this.lastTime = Date.now();
      const log = this.record.$getBuffer();
      const sendToUrls = {};
      log.forEach(item => {
        item.topo.forEach((url) => {
          let host = 'ws://' + getHost(url);
          sendToUrls[host] = sendToUrls[host] || [];
          sendToUrls[host].push({
            ...item,
            topo: [url]
          });
        })
      })
      Object.keys(sendToUrls).forEach(async (url) => {
        const ws = this.getWs(url);
        if (!ws) {
          return;
        }
        if (ws.$status.status == "wait") {
          await new Promise((resolve) => {
            setTimeout(() => {
              resolve();
            }, 1000);
          });
        } else if (
          ws.$status.status == "close" ||
          ws.$status.status == "error"
        ) {
          return;
        }
        await ws.send(
          checkJSON({
            eventName: "each_process",
            params: {
              code: url,
              log: sendToUrls[url]
            },
          }), {
            to: "ws-event-handler",
          }
        );
      });
    } catch (e) {
      process.env.NODE_ENV !== "production" && console.$log("init-proc:>>>", e);
    }
  }
}
if (typeof sessionStorage !== "undefined") {
  opened = sessionStorage.getItem("logic-topo-opened-windows") ?
    JSON.parse(sessionStorage.getItem("logic-topo-opened-windows")) : {};
}

async function openBrowser(url) {
  console.$log(orange(">>>:"), " 调试窗口 : ", orange(url));
  const ws = this.getWs(url);
  if (!ws || ws.$status.status == "close" || ws.$status.status == "error") {
    return;
  }
  try {
    const res = await ws.send(
      stringifyCirircular({
        eventName: "getStorage",
        params: {},
      }), {
        to: "task-stream",
      }
    );
    opened = Object.assign(opened, JSON.parse(res.response) || {});
    if (opened[url]) return;
    opened[url] = true;
    if (ws.$status.status == "wait") {
      await new Promise((resolve) => {
        setTimeout(() => {
          resolve();
        }, 1000);
      });
    }
    if (ws.$status.status == "open" || ws.$status.status == "ready") {
      ws.send(
        stringifyCirircular({
          eventName: "setStorage",
          params: opened,
        }), {
          to: "task-stream",
        }
      );
    }
    if (typeof window != "undefined") {
      sessionStorage.setItem(
        "logic-topo-opened-windows",
        stringifyCirircular(opened)
      );
      window.open(url);
    } else {
      try {
        const opn = eval(`require`)("opn");
        opn && opn(url);
      } catch (e) {
        console.$log("open browser error: ", e);
      }
    }
  } catch (e) {
    console.$log("open browser error: ", e);
  }
}

function createReg(start, end) {
  class Exp {
    constructor(start, end) {
      this.start = start;
      this.end = end;
      console.log(start, end, '??????>>>>>>>')
    }
    exec(str) {
      str = str.trim();
      const start = str.indexOf(this.start);
      const end = str.lastIndexOf(this.end);
      if (start == -1 || end == -1) return null;
      if (start == 0 && end == str.length - this.end.length) {
        return [str.slice(0, this.start.length), str.slice(this.start.length, str.length - this.end.length).trim(), str.slice(end)];
      }
    }
  }
  return new Exp(start, end);
}

function plugin(...arg) {
  if (typeof arg[0] == "string") {
    const pos = arg[0].trim();
    const url = {};
    let flag = '';
    Object.keys(this.regUrlMap).forEach((key) => {
      const reg = this.regUrlMap[key][0];
      const res = reg.exec(pos);
      if (res) {
        let [start, str, end] = res;
        flag = `${start} ${end}`;
        str.split(',').forEach((item) => {
          this.regUrlMap[key].slice(1).forEach((ln) => {
            url[item] = url[item] || new Set();
            url[item].add(ln);
          });
        })
      }
    });
    return [url, flag];
  }
}
class Connecter extends RecordWS {
  constructor(option = {}) {
    super();
    this.record = new Record();
    this.url = option.topo;
    this.option = {
      name: "console",
      start: "#",
      end: "#",
      open: true,
      ...option,
    };
    this.throttle = false;
    this.name = new Set();
    this.regUrlMap = {};

    if (!console.$log) {
      console.$log = console.log;
      let that = this;
      console.log = function (...arg) {
        console.$log.call(console, ...arg);
        let [result, flag] = plugin.call(that, ...arg);
        if (result) {
          Object.keys(result).forEach((k) => {
            that.record.$afterEach({
              id: flag + ':' + k,
              topo: [...result[k]],
              from: k,
              target: k,
              val: arg[2],
              args: arg[1],
              ...arg[3]
            });
          });
          that.eachProcess();
        }
      };
      console.$time = console.time;
      console.time = function (...arg) {
        let [result, flag] = plugin.call(that, ...arg);
        if (result) {
          Object.keys(result).forEach((k) => {
            that.record.$beforeEach({
              id: flag + ':' + k,
              topo: [...result[k]],
              from: k,
              target: k,
              args: arg[1],
              ...arg[2]
            });
          });
        }
        console.$time.call(console, ...arg);
      };
      console.$timeEnd = console.timeEnd;
      console.timeEnd = function (...arg) {
        let [result, flag] = plugin.call(that, ...arg);
        if (result) {
          Object.keys(result).forEach((k) => {
            that.record.$afterEach({
              id: flag + ':' + k,
              topo: [...result[k]],
              from: k,
              target: k,
              val: arg[1],
              ...arg[2]
            });
          });
          that.eachProcess();
        }
        console.$timeEnd.call(console, ...arg);
      };
    }
  }
  console(option) {
    option = option || {};
    option = {
      ...this.option,
      ...option,
    };
    option.start = option.start.trim();
    option.end = option.end.trim();
    if (option.start.length != 1 || option.end.length != 1) {
      throw new Error("start/end只能是长度为1的非空字符串");
    }
    if (typeof option.topo == 'string') {
      option.topo = option.topo.trim();
    }
    if (!Array.isArray(option.topo)) {
      option.topo = [option.topo];
    }
    let flag = `${option.start} ${option.end}`;
    this.regUrlMap[flag] = this.regUrlMap[flag] || [
      createReg(option.start, option.end),
    ];
    let {
      name
    } = option;
    name = name || /#\/topo([^?]+)\??/.exec(topo)[1];
    this.name.add(name);
    const initHost = (topo) => {
      if (!this.regUrlMap[flag].includes(topo)) {
        this.regUrlMap[flag].push(topo);
      }
      this.init(getHost(topo)).catch((e) => {
        console.$log(e);
      });
      openBrowser.call(this, topo);
    };
    option.topo.forEach((topo) => {
      initHost(topo);
    });
    return this;
  }
}
module.exports = new Connecter();